#!/usr/bin/env python

# ptweet.py
# A easy to use and configure twitter client for text-terminals

#Copyright (C) 2008 Michael Schneider 
#Email: mischneider1 at gmail dot com
#This software program is under the GNU GPL 2.0
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__author__ = 'Michael Schneider'
__url__ = 'http://code.google.com/p/ptweet/'
__version__ = '0.1.1'

from StringIO import StringIO
from datetime import datetime
import webbrowser
import urllib
import sys, os
import time

import stfl
from twitter import Twitter
from twitter.api import TwitterError
from configobj import ConfigObj
from validate import Validator

def validate_config_file(cfilename = None):
    specfile=StringIO("""
username = string()
password = string()

refresh-rate = integer(0,100, default=15)
browser = string(default="lynx")

headline = string_list(min=2, max=2, default=list("blue", "white"))
background = string_list(min=2, max=2, default=list("blue", "white"))
newentry = string_list(min=2, max=2, default=list("blue", "white"))
helpmsg = string_list(min=2, max=2, default=list("blue", "white"))
textview = string_list(min=2, max=2, default=list("blue", "white"))
""")

    cfilestring=StringIO("""
username = ""
password = ""

refresh-rate = 20
browser = "lynx"

textview = blue,yellow

""")

    if cfilename == None:
        cfile = cfilestring
    else:
        cfile = os.path.expanduser("~/%s") % cfilename

    val = Validator()
    configspec = ConfigObj(specfile, encoding="UTF8", list_values=False)

    try:
        config = ConfigObj(cfile, configspec=configspec, file_error=True)
    except IOError:
        print "The config-file could not be found"
        print "Please create %s with at least your twitteraccount" % cfile
        print "informations. For further informations look at the Readme file"
        sys.exit(1)

    test = config.validate(val)
    if test == True:
        pass
    elif test == False:
        print 'No values present!'
        os.exit()
    else:
        for entry in test:
            if test[entry] == False:
                if entry in ["username", "password"]:
                    print '"%s" are necessary in the config.' % entry
                else:
                    print 'option "%s" format wrong.' % entry
        sys.exit(1)
    return config

layout="""
vbox
  @style_normal:bg=blue,fg=white
  @headline#style_normal:bg=blue,fg=yellow
  @textview#style_normal:bg=blue,fg=white
  @helpmsg#style_normal:bg=blue,fg=white
  @statusmsg#style_normal:bg=blue,fg=white
  @tweettext#style_normal:bg=blue,fg=white
  table
    label#headline
        .colspan:2 .expand:0 .tie:c .spacer:True
        text:"ptweet"
    tablebr

    list[entrylist]
        .colspan:2 .border:rltb
        pos_name[listposname]:
        pos[listpos]:0
    tablebr

    textview[tweetview]#textview
        .colspan:2
        .border:rltb
        .richtext:1
    tablebr

    label#tweettext
        .border:ltb .expand:0
        text:"New Statusmessage: "

    !input[entry]#tweettext
        .border:rtb .expand:h
        style_focus:fg=black
        text[tweettext]#tweettext:
    tablebr
    
    label#statusmsg
        .colspan:2 .expand:0 .tie:l
        text[statusmsg]#statusmsg:
    tablebr

    label#helpmsg
        .colspan:2 .expand:0 .tie:r
        text[helpmsg]#helpmsg:
"""

class Tweet(object):
    def __init__(self, nr, username, text, date, twobject, stobject):
        self.nr = nr
        self.username = username.encode("utf-8").replace("\'", " ")
        self.text = text.encode("utf-8").replace("\'"," ")
        # Some problems with locales and the day of the week
        self.date = datetime.strptime(date[4:], "%b %d %H:%M:%S +0000 %Y")
        self.twobject = twobject
        self.stobject = stobject

    def _split_by(self, theline, n, lastfield=False):
        pieces = [theline[k:k+n] for k in xrange(0, len(theline), n)]
        if not lastfield and len(pieces[-1]) < n:
            pieces.pop()
        return pieces

    def _fractSec(self, s):
        years, s = divmod(s, 31556952)
        min, s = divmod(s, 60)
        h, min = divmod(min, 60)
        d, h = divmod(h, 24)
        if min < 10:
            min = "0%d" % min
        if h < 10:
            h = "0%d" % h
        return "%s:%s" % (str(h), str(min))

    def give_listitem(self):
        li = "{listitem text[%i]:'%s: %s'}" % (self.nr, self.username, self.text)
        return li
    def give_feedview(self):
        datediff = datetime.utcnow() - self.date
        days = str(datediff.days)
        tme = self._fractSec(datediff.seconds)
        posted_ago = "Posted %sh ago" % tme

        code = "{textview[tweetview]"
        code += "{listitem text:'%s'}" % self.username
        code += "{listitem text:' '}"
        for textpiece in self._split_by(self.text, 50, lastfield=True):
            if textpiece.startswith(" "):
                textpiece = textpiece[1:]
            code += "{listitem text:'%s'}" % textpiece
        code += "{listitem text[posted_ago%i]:'%s'}" % (self.nr, posted_ago)
        code += "}"
        return code

    def send_response(self, text):
        self.twobject.direct_messages.new(
            user = self.username,
            text = text)

    def update_layout_textview(self):
        code = self.give_feedview()
        self.stobject.modify("tweetview", "replace_inner", code)

    def __str__(self):
        return "Tweet: %s: %s" % (self.username, self.text)

class TweetList(object):
    def __init__(self, twobject, stobject):
        self.twobject = twobject
        self.stobject = stobject
        self.tweets = None
        # Receive tweets 
        self.update_tweets()

    def _fetch_tweets(self):
        """
        Fetch the tweets from the twitter account 
        and save it as Tweet objects internally
        """
        self.tweets = [Tweet(nr, tweet['user']['screen_name'], tweet['text'], tweet['created_at'], self.twobject, self.stobject) for nr, tweet in  enumerate(self.twobject.statuses.friends_timeline())]

    def update_layout_list(self):
        """
        Update the layout list with the actual tweets
        """
        code = "{list"
        for tweet in self.tweets:
            code += tweet.give_listitem()
        code += "}"

        self.stobject.modify("entrylist", "replace_inner", code)
        self.stobject.set("listpos", "0")

    def update_tweets(self):
        """
        Fetch the feets and update the layout list
        """
        self._fetch_tweets()
        self.update_layout_list()

    def get_tweets(self):
        """
        Return all tweets as a list
        """
        if self.tweets == None:
            print "No tweetlist available"
            os.exit()
        else:
            return self.tweets

    def get_tweet(self, pos):
        """
        Return a specific tweet
        """
        if self.tweets == None:
            print "No tweetlist available"
            os.exit()
        else:
            if isinstance(pos, str):
                return self.tweets[int(pos)]
            else:
                return self.tweets[pos]

def new_status_message(text):
    tw.statuses.update(
            status=text
            )

def send_direct_message(username, text):
    tw.direct_messages.new(
            user = username,
            text=text)

def sethelp(text):
    """
    Set the helptext
    """
    focus = st.get_focus()
    st.set("helpmsg", text)

def setstatus(text):
    """
    Set the statustext
    """
    focus = st.get_focus()
    st.set("statusmsg", " " + text)

if __name__ == '__main__':

    
    config = validate_config_file(cfilename = ".ptweetrc")

    # Refreshtime in ms
    # 1 sec = 1000 ms
    TIMEOUT = 900000

    st = stfl.create(layout)

    try:
        tw = Twitter(config["username"], config["password"] )
    except TwitterError, e:
        print e
        os.exit(1)

    tweetlist = TweetList(tw, st)
    sethelp("[ ESC = exit ]")
    #sethelp("[ h = help | ESC = exit ]")

    while True:

        event = st.run(TIMEOUT)
        focus = st.get_focus()

        if event == "ESC":
            break

        # Refresh the tweetlist after a timeout
        if event == "TIMEOUT":
            tweetlist.update_tweets()
            setstatus("Update tweetlist")

        # Refresh the tweetlist with F4
        if event == "F4":
            tweetlist.update_tweets()
            setstatus("Status update success")

        # Update textview in layout
        if event == "ENTER" and focus and focus == "entrylist":
            listpos = st.get("listpos")
            tweet = tweetlist.get_tweet(listpos)
            tweet.update_layout_textview()

        # Show next tweet and update the tweetview
        if event == "n" and focus and focus == "entrylist":
            listpos = st.get("listpos")
            newlistpos = str(int(listpos) + 1)
            st.set("listpos", newlistpos)
            tweet = tweetlist.get_tweet(newlistpos)
            tweet.update_layout_textview()

        # Show previous tweet and update the tweetview
        if event == "p" and focus and focus == "entrylist":
            listpos = st.get("listpos")
            if int(listpos)  == 0:
                pass
            else:
                newlistpos = str(int(listpos) - 1)
                st.set("listpos", newlistpos)
                tweet = tweetlist.get_tweet(newlistpos)
                tweet.update_layout_textview()

        # Send a message
        if event =="ENTER" and focus and focus == "entry":
            tweettext = st.get("tweettext")
            if tweettext != "":
                if tweettext.startswith("@"):
                    # With a @ before the text: send a direct message
                    username = tweettext.split()[0][1:]
                    text = tweettext.split()[1:]
                    send_direct_message(username, text)
                    setstatus("Direct message was send to %s" % username)
                else:
                    # besides send a status message
                    new_status_message(tweettext)
                    setstatus("Status update success")
                    st.set("tweettext", "")

        # Send a response message
        if event == "r" and focus and focus == "entrylist":
            listpos = st.get("listpos")
            text = st.get("tweettext")
            if tweettext != "":
                tweet = tweetlist.get_tweet(listpos)
                tweet.send_response(text)
                setstatus("Response message was send %s" % tweet.username)
            else:
                setstatus("No text entry")

        # Open tweetline of the user in browser
        if event == "o" and focus and focus == "entrylist":
            listpos = st.get("listpos")
            entry = st.get(str(listpos))
            username = entry.split(":")[0]
            #username = tweetlist.get_tweet(listpos).username
            url = "http://twitter.com/%s" % username
            browser = webbrowser.get(config["browser"])
            browser.open(url)
